package org.jeecgframework.minidao.aop;

import java.io.File;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ognl.Ognl;
import ognl.OgnlException;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.engine.jdbc.internal.BasicFormatterImpl;
import org.jeecgframework.minidao.annotation.Arguments;
import org.jeecgframework.minidao.annotation.ResultType;
import org.jeecgframework.minidao.annotation.Sql;
import org.jeecgframework.minidao.def.MiniDaoConstants;
import org.jeecgframework.minidao.hibernate.dao.IGenericBaseCommonDao;
import org.jeecgframework.minidao.pojo.MiniDaoPage;
import org.jeecgframework.minidao.spring.rowMapper.GenericRowMapper;
import org.jeecgframework.minidao.spring.rowMapper.MiniColumnMapRowMapper;
import org.jeecgframework.minidao.spring.rowMapper.MiniColumnOriginalMapRowMapper;
import org.jeecgframework.minidao.util.FreemarkerParseFactory;
import org.jeecgframework.minidao.util.MiniDaoUtil;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.simple.ParameterizedBeanPropertyRowMapper;



/**
 * 
 * @Title:MiniDaoHandler
 * @description:MiniDAO 拦截器
 * @author 张代浩
 * @mail zhangdaiscott@163.com
 * @category www.jeecg.org
 * @date 20130817
 * @version V1.0
 */
public class MiniDaoHandler implements MethodInterceptor {
	private static final Logger logger = Logger.getLogger(MiniDaoHandler.class);
	private JdbcTemplate jdbcTemplate;
	private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
	private IGenericBaseCommonDao miniDaoHiberCommonDao;
	
	private BasicFormatterImpl formatter = new BasicFormatterImpl();
	
	private String UPPER_KEY = "upper";
	private String LOWER_KEY = "lower";
	
	/**
	 * map的关键字类型  三个值
	 */
	private String keyType = "origin";
	private boolean formatSql = false;
	private boolean showSql = false;
	private String dbType;

	public Object invoke(MethodInvocation methodInvocation) throws Throwable {
		Method method = methodInvocation.getMethod();
		Object[] args = methodInvocation.getArguments();
		//返回结果
		Object returnObj = null;
		//SQL模板
		String templateSql = null;
		//SQL模板参数
		Map<String, Object> sqlParamsMap = new HashMap<String, Object>();
		//分页参数
		MiniDaoPage pageSetting = new MiniDaoPage();
		
		//check.1:判断是否是抽象方法，如果是非抽象方法，则不执行MiniDao拦截器
		if (!MiniDaoUtil.isAbstract(method)) {
	            return methodInvocation.proceed();
	    }
		//Step.0 判断是否是Hiber实体维护方法，如果是执行Hibernate方式实体维护
		Map<String,Object> rs = new HashMap<String,Object>();
		if(miniDaoHiber(rs, method, args)){
			return rs.get("returnObj");
		}
		//Step.1装载SQL模板，所需参数
		templateSql = installDaoMetaData(pageSetting,method, sqlParamsMap, args);
	
		//Step.3解析SQL模板，返回可执行SQL
		String executeSql = parseSqlTemplate(method, templateSql, sqlParamsMap);

		//Step.4 组装SQL占位符参数
		Map<String,Object> sqlMap = installPlaceholderSqlParam(executeSql,sqlParamsMap);
		
		//Step.5 获取SQL执行返回值
		returnObj = getReturnMinidaoResult(dbType,pageSetting,jdbcTemplate, method, executeSql,sqlMap);
		//TODO SQL缓存机制
		if(showSql){
			logger.info("MiniDao-SQL:\n\n"+(formatSql == true?formatter.format(executeSql):executeSql)+"\n");
		}
		return returnObj;
	}
	
	
	
	/**
	 * MiniDao支持实体维护
	 * 说明：向下兼容Hibernate实体维护方式,实体的增删改查SQL自动生成,不需要写SQL
	 * @param returnObj
	 * @param method
	 * @param args
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private boolean miniDaoHiber(Map rs,Method method,Object[] args){
		//是否采用Hibernate方式，进行实体维护，不需要生成SQL
		//boolean arguments_flag = entity.isAnnotationPresent(Entity.class);
		//判断如果是持久化对象，则调用Hibernate进行持久化维护
		if(MiniDaoConstants.METHOD_SAVE_BY_HIBER.equals(method.getName())){
			 miniDaoHiberCommonDao.save(args[0]);
			 return true;
		}
		if(MiniDaoConstants.METHOD_GET_BY_ID_HIBER.equals(method.getName())){
			 //获取Dao方法与实体配置
			 Class<?> clz = (Class<?>) args[0];
			 rs.put("returnObj", miniDaoHiberCommonDao.get(clz, args[1].toString()));
			 return true;
		}
		if(MiniDaoConstants.METHOD_GET_BY_ENTITY_HIBER.equals(method.getName())){
			 //获取主键名
			 rs.put("returnObj", miniDaoHiberCommonDao.get(args[0]));
			 return true;
		}
		if(MiniDaoConstants.METHOD_UPDATE_BY_HIBER.equals(method.getName())){
			 miniDaoHiberCommonDao.saveOrUpdate(args[0]);
			 return true;
		}
		if(MiniDaoConstants.METHOD_DELETE_BY_HIBER.equals(method.getName())){
			 miniDaoHiberCommonDao.delete(args[0]);
			 return true;
		}
		if(MiniDaoConstants.METHOD_DELETE_BY_ID_HIBER.equals(method.getName())){
			 Class<?> clz = (Class<?>) args[0];
			 miniDaoHiberCommonDao.deleteEntityById(clz, args[1].toString());
			 return true;
		}
		if(MiniDaoConstants.METHOD_LIST_BY_HIBER.equals(method.getName())){
			 rs.put("returnObj", miniDaoHiberCommonDao.loadAll(args[0]));
			 return true;
		}
		return false;
	}
	
	/**
	 * 解析SQL模板
	 * 
	 * @param method
	 * @param templateSql
	 * @param sqlParamsMap
	 * @return  可执行SQL
	 */
	private String parseSqlTemplate(Method method,String templateSql,Map<String,Object> sqlParamsMap){
		// step.1.根据命名规范[接口名_方法名.sql]，获取SQL模板文件的路径
		String executeSql = null;
		
		// step.2.获取SQL模板内容
		// step.3.通过模板引擎给SQL模板装载参数,解析生成可执行SQL
		if(StringUtils.isNotEmpty(templateSql)){
			executeSql = new FreemarkerParseFactory().parseTemplateContent(templateSql, sqlParamsMap);
		}else{
			// update-begin--Author:fancq  Date:20131225 for：sql放到dao层同样目录
			// update-begin--Author:zhaojunfu  Date:20140418 for：扫描规则-首先扫描同位置sql目录,如果没找到文件再搜索dao目录
			String sqlTempletPath = "/"+method.getDeclaringClass().getName().replace(".", "/").replace("/dao/", "/sql/")+"_"+method.getName()+".sql";
			URL sqlFileUrl = this.getClass().getClassLoader().getResource(sqlTempletPath);
			if(sqlFileUrl==null){
				sqlTempletPath = "/"+method.getDeclaringClass().getName().replace(".", "/")+"_"+method.getName()+".sql";
			}
			// update-end--Author:fancq  Date:20131225 for：sql放到dao层同样目录
			// update-end--Author:zhaojunfu  Date:20140418 for：扫描规则：首先扫描同位置sql目录,如果没找到文件再搜索dao目录
			logger.debug("MiniDao-SQL-Path:"+sqlTempletPath);
			executeSql = new FreemarkerParseFactory().parseTemplate(sqlTempletPath, sqlParamsMap);
		}
		return getSqlText(executeSql);
	}
	
	
	/**
	 * 除去无效字段，不然批量处理可能报错
	 */
	private String getSqlText(String sql) {
		return sql.replaceAll("\\n", " ").replaceAll("\\t", " ")
				.replaceAll("\\s{1,}", " ").trim();
	}
	
	
	/**
	 * 组装占位符参数 -> Map
	 * @param executeSql
	 * @return
	 * @throws OgnlException 
	 */
	private Map<String,Object> installPlaceholderSqlParam(String executeSql,Map sqlParamsMap) throws OgnlException{
		Map<String,Object> map = new HashMap<String,Object>();
		String regEx = ":[ tnx0Bfr]*[0-9a-z.A-Z]+"; // 表示以：开头，[0-9或者.或者A-Z大小都写]的任意字符，超过一个
		Pattern pat = Pattern.compile(regEx);
		Matcher m = pat.matcher(executeSql);
		while (m.find()) {
			logger.debug(" Match [" + m.group() +"] at positions " + m.start() + "-" + (m.end() - 1));
			String ognl_key = m.group().replace(":","").trim();
			map.put(ognl_key, Ognl.getValue(ognl_key, sqlParamsMap));
		}
		return map;
	}
	
	/**
	 *  获取MiniDao处理结果集
	 * @param dbType 
	 * @param pageSetting 
	 * @param jdbcTemplate
	 * @param method
	 * @param executeSql
	 * @return  结果集
	 */
	private Object getReturnMinidaoResult(String dbType, MiniDaoPage pageSetting, JdbcTemplate jdbcTemplate,Method method,String executeSql,Map<String,Object> paramMap){
		// step.4.调用SpringJdbc引擎，执行SQL返回值
		// 5.1获取返回值类型[Map/Object/List<Object>/List<Map>/基本类型]
		String methodName = method.getName();
		// 判斷是否非查詢方法
		if (checkActiveKey(methodName)) {
			if(paramMap!=null){
				return namedParameterJdbcTemplate.update(executeSql, paramMap);
			}else{
				return jdbcTemplate.update(executeSql);
			}
		} else if (checkBatchKey(methodName)) {
			return batchUpdate(jdbcTemplate,executeSql);
		} else {
			// 如果是查詢操作
			Class<?> returnType = method.getReturnType();
			if (returnType.isPrimitive()) {
				Number number = jdbcTemplate.queryForObject(executeSql,BigDecimal.class);
				if ("int".equals(returnType)) {
					return number.intValue();
				} else if ("long".equals(returnType)) {
					return number.longValue();
				} else if ("double".equals(returnType)) {
					return number.doubleValue();
				}
			} else if (returnType.isAssignableFrom(List.class)) {
				// update-begin--Author:fancq  Date:20140102 for：支持多数据分页
				int page = pageSetting.getPage();
				int rows = pageSetting.getRows();
				if(page!=0 && rows!=0){
					executeSql = MiniDaoUtil.createPageSql(dbType,executeSql, page, rows);
				}
				// update-begin--Author:fancq  Date:20140102 for：支持多数据分页
				// update-begin--Author:fancq  Date:20131219 for：支持返回Map和实体 list
				ResultType resultType = method.getAnnotation(ResultType.class);
				String[] values = null;
				if (resultType != null) {
					values = resultType.value();
				}
				if (values == null || values.length == 0 || "java.util.Map".equals(values[0])) {
					if(paramMap!=null){
						return namedParameterJdbcTemplate.query(executeSql, paramMap,getColumnMapRowMapper());
					}else{
						return jdbcTemplate.query(executeSql,getColumnMapRowMapper());
					}
				} else {
					Class clazz = null;
					try {
						clazz = Class.forName(values[0]);
					} catch (Exception e) {
						e.printStackTrace();
					}
					if(paramMap!=null){
						return namedParameterJdbcTemplate.query(executeSql, paramMap, new GenericRowMapper(clazz));
					}else{
						return jdbcTemplate.query(executeSql, new GenericRowMapper(clazz));
					}
				}
				// update-end--Author:fancq  Date:20131219 for：支持返回Map和实体 list
			} else if (returnType.isAssignableFrom(Map.class)) {
				//Map类型
				if(paramMap!=null){
					return (Map)namedParameterJdbcTemplate.queryForObject(executeSql, paramMap,getColumnMapRowMapper());
				}else{
					return (Map)jdbcTemplate.queryForObject(executeSql,getColumnMapRowMapper());
				}
			} else if (returnType.isAssignableFrom(String.class)) {
				//String类型
				try{  
					if(paramMap!=null){
						return namedParameterJdbcTemplate.queryForObject(executeSql, paramMap, String.class);
					}else{
						return jdbcTemplate.queryForObject(executeSql,String.class);
					}
		        }catch (EmptyResultDataAccessException e) {  
		            return null;  
		        }
			}else if(MiniDaoUtil.isWrapClass(returnType)){
				//基本类型的包装类
				try{  
					if(paramMap!=null){
						return namedParameterJdbcTemplate.queryForObject(executeSql, paramMap, returnType);
					}else{
						return jdbcTemplate.queryForObject(executeSql, returnType);
					}
		        }catch (EmptyResultDataAccessException e) {  
		            return null;  
		        }
			} else {
				// 对象类型
				RowMapper<?> rm = ParameterizedBeanPropertyRowMapper.newInstance(returnType);
				try{  
					if(paramMap!=null){
						return namedParameterJdbcTemplate.queryForObject(executeSql, paramMap, rm);
					}else{
						return jdbcTemplate.queryForObject(executeSql, rm);
					}
		        }catch (EmptyResultDataAccessException e) {  
		            return null;  
		        }
			}
		}
		return null;
	}

	/**
	 * 批处理
	 *@Author JueYue
	 *@date   2013-11-17
	 *@return
	 */
	private int[] batchUpdate(JdbcTemplate jdbcTemplate, String executeSql) {
		String[] sqls = executeSql.split(";");
		if(sqls.length<100){
			return jdbcTemplate.batchUpdate(sqls);
		}
		int[] result = new int[sqls.length];
		List<String> sqlList = new ArrayList<String>();
		for(int i = 0;i<sqls.length;i++){
			sqlList.add(sqls[i]);
			if(i%100 == 0){
				addResulArray(result,i+1,jdbcTemplate.batchUpdate(sqlList.toArray(new String[0])));
				sqlList.clear();
			}
		}
		addResulArray(result,sqls.length,jdbcTemplate.batchUpdate(sqlList.toArray(new String[0])));
		return result;
	}


	/**
	 * 把批量处理的结果拼接起来
	 *@Author JueYue
	 *@date   2013-11-17
	 */
	private void addResulArray(int[] result,int index, int[] arr) {
		int length = arr.length;
		for(int i = 0;i<length;i++){
			result[index-length + i] = arr[i];
		}
	}



	public NamedParameterJdbcTemplate getNamedParameterJdbcTemplate() {
		return namedParameterJdbcTemplate;
	}



	public void setNamedParameterJdbcTemplate(
			NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
		this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
	}



	/**
	 * 装载SQL模板参数
	 * @param pageSetting 
	 * 
	 * @param method
	 * @param sqlParamsMap 返回(装载模板参数)
	 * @param args
	 * @return   templateSql(@SQL标签的SQL)
	 * @throws Exception
	 */
	private String installDaoMetaData(MiniDaoPage pageSetting, Method method,Map<String, Object> sqlParamsMap,Object[] args) throws Exception{
		String templateSql = null;
		//如果方法参数大于1个的话，方法必须使用注释标签Arguments
		boolean arguments_flag = method.isAnnotationPresent(Arguments.class);
		if(arguments_flag){
			//[1].获取方法的参数标签
			Arguments arguments = method.getAnnotation(Arguments.class);  
            logger.debug("@Arguments------------------------------------------"+Arrays.toString(arguments.value()));
            if(arguments.value().length > args.length){
            	//校验机制-如果注释标签参数数目大于方法的参数，则抛出异常
            	throw new Exception("[注释标签]参数数目，不能大于[方法参数]参数数目");
            }
            // step.2.将args转换成键值对，封装成Map对象
            int args_num = 0;
            for(String v:arguments.value()){
            	// update-begin--Author:fancq  Date:20140102 for：支持多数据分页
            	if (v.equalsIgnoreCase("page")) {
            		pageSetting.setPage(Integer.parseInt(args[args_num].toString()));
            	}
            	if (v.equalsIgnoreCase("rows")) {
            		pageSetting.setRows(Integer.parseInt(args[args_num].toString()));
            	}
            	// update-end--Author:fancq  Date:20140102 for：支持多数据分页
            	sqlParamsMap.put(v, args[args_num]);
            	args_num++;
            }
		}else{
			//如果未使用[参数标签]
			if(args.length>1){
				throw new Exception("方法参数数目>=2，方法必须使用注释标签@Arguments");	
			}else if(args.length==1){
				// step.2.将args转换成键值对，封装成Map对象
				sqlParamsMap.put(MiniDaoConstants.SQL_FTL_DTO, args[0]);
			}
			
		}
		
		//[2].获取方法的SQL标签
		if(method.isAnnotationPresent(Sql.class)){
			Sql sql = method.getAnnotation(Sql.class);
			//如果用户采用自定义标签SQL，则SQL文件无效
			if(StringUtils.isNotEmpty(sql.value())){
				templateSql = sql.value();
			}
            logger.debug("@Sql------------------------------------------"+sql.value());  
		}
		return templateSql;
	}
	

	/**
	 * 判斷是否是執行的方法（非查詢）
	 * 
	 * @param methodName
	 * @return
	 */
	private static boolean checkActiveKey(String methodName) {
		String keys[] = MiniDaoConstants.INF_METHOD_ACTIVE.split(",");
		for (String s : keys) {
			if (methodName.startsWith(s))
				return true;
		}
		return false;
	}

	/**
	 * 判斷是否批處理
	 * 
	 * @param methodName
	 * @return
	 */
	private static boolean checkBatchKey(String methodName) {
		String keys[] = MiniDaoConstants.INF_METHOD_BATCH.split(",");
		for (String s : keys) {
			if (methodName.startsWith(s))
				return true;
		}
		return false;
	}
	/**
	 *根据参数设置map的key大小写
	 **/
	private RowMapper<Map<String,Object>> getColumnMapRowMapper() {
		if(getKeyType().equalsIgnoreCase(LOWER_KEY)){
			return new MiniColumnMapRowMapper();
		}else if(getKeyType().equalsIgnoreCase(UPPER_KEY)){
			return new ColumnMapRowMapper();
		}else{
			return new MiniColumnOriginalMapRowMapper();
		}
	}
	
	public JdbcTemplate getJdbcTemplate() {
		return jdbcTemplate;
	}

	public IGenericBaseCommonDao getMiniDaoHiberCommonDao() {
		return miniDaoHiberCommonDao;
	}

	public void setMiniDaoHiberCommonDao(IGenericBaseCommonDao miniDaoHiberCommonDao) {
		this.miniDaoHiberCommonDao = miniDaoHiberCommonDao;
	}

	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	public boolean isFormatSql() {
		return formatSql;
	}

	public void setFormatSql(boolean formatSql) {
		this.formatSql = formatSql;
	}



	public String getKeyType() {
		return keyType;
	}



	public void setKeyType(String keyType) {
		this.keyType = keyType;
	}

	public void setShowSql(boolean showSql) {
		this.showSql = showSql;
	}



	public String getDbType() {
		return dbType;
	}



	public void setDbType(String dbType) {
		this.dbType = dbType;
	}
	
	
}
